/***************************************************************************\
 * The Mattise Kernel														*
 * Copyright 2007 Matthew Iselin											*
 * Licensed under the GPL													*
 *																			*
 * syscall.cc																*
 *																			*
 * Implementation of system calls, all based on interrupt 0x80 (128d)		*
 *																			*
\***************************************************************************/

// Includes
#include <system.h>
#include <stdio.h>
#include <task.h>
#include <io.h>
#include <memory.h>
#include <syscall.h>
#include <string.h>
#include <console.h>
#include <pm.h>
#include <paging.h>

// Defines

// these change the way message passing work
#define USEPAGEALLOCATOR
// #define USEKERNELHEAP

// Prototypes

/// Extends a process's heap
uint_t syscall_sbrk( int pid, int amt, int map = 1, int pages = 0 );

// Constants

// simple stuff
#define SYSCALL_EXIT		0
#define SYSCALL_PRINTSTR	1
#define SYSCALL_PRINTCHAR	2

// device drivers use these - OBSOLETE
#define SYSCALL_INSTDEVICE	3
#define SYSCALL_INSTFUNCTS	4
#define SYSCALL_GETDEVID	5
#define SYSCALL_GETDEVNAME	6
#define SYSCALL_DESTROYDEV	7
#define SYSCALL_DUMPDEVS	8

// thread creation
#define SYSCALL_CRTTHREAD	3

// device/file i/o
#define SYSCALL_OPEN		9
#define SYSCALL_CLOSE		10
#define SYSCALL_FSTAT		11
#define SYSCALL_ISATTY		12
#define SYSCALL_LINK		13
#define SYSCALL_LSEEK		14
#define SYSCALL_READ		15
#define SYSCALL_STAT		16
#define SYSCALL_UNLINK		17
#define SYSCALL_WRITE		18

// environment control
#define SYSCALL_ENVIRON		19

// process control
#define SYSCALL_EXECVE		20
#define SYSCALL_FORK		21
#define SYSCALL_GETPID		22
#define SYSCALL_KILL		23
#define SYSCALL_TIMES		24
#define SYSCALL_WAIT		25

// heap space
#define SYSCALL_SBRK		26

// messaging
#define SYSCALL_SENDMSG		27
#define SYSCALL_RECVMSG		28
#define SYSCALL_HASMSG		29
#define SYSCALL_MSGSIZE		30

// timeslice
#define SYSCALL_TRESET		31

// IRQ redirection
#define SYSCALL_REGFORIRQ	32
#define SYSCALL_DEREGIRQ	33

// console framebuffer sharing
#define SYSCALL_SHAREFB		34

// userspace heap memory allocation
#define SYSCALL_UMALLOC	35
#define SYSCALL_UREALLOC	36
#define SYSCALL_UFREE			37

// Functions

/// Extends a process's heap
uint_t syscall_sbrk( int pid, int amt, int map, int pages )
{
	// get the current end
	uint_t img_end = GetImageEnd( pid );
	
	// do we want pages?
	if( pages )
	{
		// calculate how many until 4096
		uint_t addend = img_end % 4096;
		
		// add it
		if( addend )
			img_end += (4096-addend);
		
		// save this for returning
		uint_t bottom = img_end;
		
		// add on the page
		img_end += 4096;
		
		// set the new end
		SetImageEnd( pid, img_end );
		
		// return the bottom of the new page
		return bottom;
	}
	else
	{
		// save it
		uint_t bottom = img_end;
		
		// increment it
		img_end += amt;
		
		// set it again
		SetImageEnd( pid, img_end );
		
		// page-align both the start and the end
		uint_t pa_bottom = bottom & ~0xFFF;
		uint_t pa_end = img_end & ~0xFFF;
		
		// map until we're done - this assumes that the memory is not already mapped...
		for( int i = pa_bottom; i <= pa_end; i += 4096 )
			MapUserPhysicalAddress( i, (uint_t) pm_alloc( 1 ), PF_PRESENT | PF_USER | PF_READWRITE );
		
		// return the new address
		return bottom;
	}
}

/// System call handler
void SysCall( struct regs* r )
{
	// what function?
	switch( r->eax )
	{
		case SYSCALL_EXIT:
		
			// kill the current process and reschedule
			kill( currpid() );
			resched();
			break;
		
		case SYSCALL_PRINTSTR:
		
			// print the string in ESI
			{
				char* esi = (char*) r->esi;
				cons.Output( esi );
			}
			break;
		
		case SYSCALL_PRINTCHAR:
		
			// print the character in ESI
			kprintf( "%c", r->esi );
			break;
			
		/** OBSOLETE
		
		case SYSCALL_INSTDEVICE:
		
			// install the device
			r->ebx = InstallDevice( (char*) r->esi, r->ecx, r->edx );
			break;
			
		case SYSCALL_INSTFUNCTS:
		
			// install the functions
			r->ebx = InstallFunctions( r->ecx, *((INSTFUNCDATA*) r->esi) );
			break;
		
		case SYSCALL_GETDEVID:
		
			// get the device id
			r->ebx = GetDeviceID( r->ecx );
			break;
		
		case SYSCALL_GETDEVNAME:
		
			// get the device name
			{
				char* tmp = (char*) kmalloc( 64 );
				GetDeviceName( r->ecx, tmp );
				r->esi = (uint_t) tmp;
			}
			break;
			
		case SYSCALL_DESTROYDEV:
		
			// destroy the device
			DestroyDevice( r->ecx );
			break;
		
		case SYSCALL_DUMPDEVS:
		
			// dump the device list
			DumpDevices();
			break;
			
		**/
		
		case SYSCALL_CRTTHREAD:
		
			// create the thread
			r->ebx = CreateThread_UserSpace( currpid(), r->esi, r->ebx, (const char*) r->edi, r->ecx );
			
			break;

		case SYSCALL_OPEN:
		
			// open the device
			r->ebx = open( (char*) r->esi, r->ecx );
			break;
		
		case SYSCALL_CLOSE:
		
			// closes a device
			r->ebx = close( r->ecx );
			break;
		
		case SYSCALL_FSTAT:
		
			// not implemented yet
			break;
		
		case SYSCALL_ISATTY:
		
			// not implemented yet
			break;
		
		case SYSCALL_LINK:
		
			// not implemented yet
			break;
		
		case SYSCALL_LSEEK:
		
			// seeks in a device
			r->ebx = lseek( r->ecx, r->edx, r->ebx );
			break;
		
		case SYSCALL_READ:
		
			// reads from a device
			r->ebx = read( r->ecx, (char*) r->esi, r->edx );
			break;
		
		case SYSCALL_STAT:
		
			// not implemented yet
			break;
		
		case SYSCALL_UNLINK:
		
			// not implemented yet
			break;
		
		case SYSCALL_WRITE:
		
			// writes to a device
			r->ebx = write( r->ecx, (char*) r->esi, r->edx );
			break;
		
		case SYSCALL_ENVIRON:
		
			// not implemented yet
			break;
		
		case SYSCALL_EXECVE:
		
			// not implemented yet
			break;
		
		case SYSCALL_FORK:
		
			// not implemented yet
			break;
		
		case SYSCALL_GETPID:
		
			// gets the current process id
			r->ebx = currpid();
			break;
		
		case SYSCALL_TIMES:
		
			{
			// not implemented yet
			struct tms
			{
				long tms_utime;
				long tms_stime;
				long tms_cstime;
				long tms_cutime;
			}
			rb;
			
			// get the time of the current process
			rb.tms_utime = GetProcessTime( currpid() );
			
			// get the time the system has used on behalf of the process
			rb.tms_stime = 0; // TODO: count this somehow
			
			// get the time used by all child processes
			rb.tms_cutime = 0; // TODO: keep track of child times
			
			// gets the time used by the system on behalf of child processes
			rb.tms_cstime = 0; // TODO: keep track of child times
			
			// copy rb across
			memcpy( (struct tms*) r->edi, &rb, sizeof( struct tms ) );
			
			// success
			r->ebx = rb.tms_utime + rb.tms_stime;
			}
			break;
		
		case SYSCALL_WAIT:
		
			// not implemented yet
			break;
		
		case SYSCALL_SBRK:
		
			r->ebx = syscall_sbrk( currpid(), r->ecx );
			break;
		
		case SYSCALL_SENDMSG: // 27
		
			{

			// validate incoming parameters
			if( r->edx <= 0 )
			{
				r->ebx = -1;
				return;
			}

			// check if we need to perform a copy
			if( r->esi < 0xE0000000 || r->esi > 0xE0000000 )
			{
				// copy it
				char* ptr = (char*) umalloc( r->edx );
				memcpy( ptr, (char*) r->esi, r->edx );
				r->esi = (uint_t) ptr;
			}

			// push it onto the message queue of the new process
			struct tagMsg msg;
			msg.srcAddr = msg.destAddr = r->esi;
			msg.srcPid = currpid();
			msg.destPid = r->ebx;
			msg.msgCode = r->ecx;
			msg.msgLen = r->edx;
			PutMessage( msg.destPid, msg );

			kprintf( "process %d sending to %d [0x%x]\n", msg.srcPid, msg.destPid, msg.msgCode );

			// success
			r->ebx = 0;

/*			// maximum of 112 pages of message data
			if( r->edx >= (4096*112) )
			{
				// we fail
				r->ebx = -1;
				return;
			}
			
			// allocate some space
#ifdef USEPAGEALLOCATOR
			uint_t __tmp_pcount = ( r->edx / 4096 ) + 1;
			char* ptr = (char*) pm_alloc( __tmp_pcount );
			
			// map it temporarily (it's okay if after this this gets messed up)
			for( int __tmp_index = 0; __tmp_index < __tmp_pcount; __tmp_index++ )
				MapPhysicalAddress( GetCurrentPageDirVirtual(), 0x10000 + (__tmp_index*4096), ((uint_t)ptr)+(__tmp_index*4096), PF_PRESENT | PF_READWRITE | PF_USER );
#else // ifdef USEKERNELHEAP
			char* ptr = (char*) kmalloc( r->edx );
#endif
			
			// copy the data into our local storage location
			memcpy( ptr, (char*) r->esi, r->edx );
			
			// fill our structure
			struct tagMsg msg;
			msg.srcAddr = (uint_t) ptr;
			msg.srcPid = currpid();
			msg.destPid = r->ebx;
			msg.msgLen = r->edx;
			msg.msgCode = r->ecx;
			
			// 'send' it
			PutMessage( msg.destPid, msg );
			
			// success
			r->ebx = 0;*/
			}
			break;
		
		case SYSCALL_RECVMSG: // 28
		
			{

			/** ebx = the address of the message - if 0 something went wrong! **/

			// check for a message
			if( !HasMessage( currpid() ) )
			{
				r->ebx = 0;
				return;
			}

			// message information buffer
			struct tagMsg msg;
			
			// get the message
			GetMessage( currpid(), &msg );

			// return information
			r->ebx = msg.destAddr;
			r->ecx = msg.msgCode;
			r->edx = msg.msgLen;
			r->esi = msg.srcPid;
			r->edi = msg.destPid;

			kprintf( "process %d receiving msg from %d [0x%x]\n", currpid(), msg.srcPid, msg.msgCode );
			kprintf( "addr1 = 0x%x, addr2 = 0x%x\n", r->ebx, msg.srcAddr );
			
			// validate...
/*			if( r->edi <= 0x400000 || r->edx <= 0 || r->edx > (4096*112) )
			{
				r->ebx = -1;
				return;
			}
			
			// check for a message
			if( !HasMessage( currpid() ) )
			{
				r->ebx = -1;
				return;
			}
			
			// message information buffer
			struct tagMsg msg;
			
			// get the message
			GetMessage( currpid(), &msg );
			
			// verify that it's a valid size
			if( r->edx < msg.msgLen )
			{
				// put it back on the queue
				PutMessage( msg.destPid, msg );
				r->ebx = -1;
				return;
			}
			
#ifdef USEPAGEALLOCATOR
			// because we use the page allocator, we must map the pages to the space between 64k and 512k
			// this mapping is temporary and it's ok if afterwards this mapping is destroyed
			uint_t __tmp_pcount = ( msg.msgLen / 4096 ) + 1;
			for( int __tmp_index = 0; __tmp_index < __tmp_pcount; __tmp_index++ )
				MapPhysicalAddress( GetCurrentPageDirVirtual(), 0x10000 + (__tmp_index*4096), msg.srcAddr + (__tmp_index*4096), PF_PRESENT | PF_READWRITE | PF_USER );
			
			// perform the copy
			memcpy( (char*) r->edi, (char*) 0x10000, msg.msgLen );
#else // ifdef USEKERNELHEAP	
			// perform the copy
			memcpy( (char*) r->edi, (char*) msg.srcAddr, msg.msgLen );
#endif
			
			// fill in the return buffer
			r->ecx = msg.msgCode;
			r->edx = msg.msgLen;
			
			// success - return the size of the message
			r->ebx = msg.msgLen;*/

			}
			break;
		
		case SYSCALL_HASMSG: // 29
		
			// check...
			r->ebx = HasMessage( currpid() );
			if( r->ebx )
				kprintf( "process %d has a message\n", currpid() );
			
			break;
		
		case SYSCALL_MSGSIZE: // 30
		
			// get the size
			if( HasMessage( currpid() ) )
				r->ebx = GetMessageSize( currpid() );
			else
				r->ebx = -1;
			
			break;
		
		case SYSCALL_TRESET: // 31
		
			// reset the timeslice
			ResetTimeslice( currpid() );
			
			// reschedule
			resched();
			
			break;
		
		case SYSCALL_REGFORIRQ: // 32
		
			// register for an IRQ
			irq_install_userland_handler( r->ecx, currpid() );
			
			// success
			r->ebx = 0;
			
			break;
		
		case SYSCALL_DEREGIRQ: // 33
		
			// register for an IRQ
			irq_remove_userland_handler( r->ecx, currpid() );
			
			// success
			r->ebx = 0;
			
			break;
		
		case SYSCALL_SHAREFB: // 34
		
			// check...
			static int isready = 1;
			if( isready )
			{
				MapUserPhysicalAddress( 0xB8000, 0xB8000, PF_PRESENT | PF_READWRITE | PF_USER );
				r->ebx = /*isready =*/ 0;
			}
			else
				r->ebx = -1;
			return;

		case SYSCALL_UMALLOC: // 35

			// return whatever umalloc would
			r->ebx = (uint_t) umalloc( r->ecx );
			return;

		case SYSCALL_UREALLOC: // 36

			// return whatever urealloc would
			r->ebx = (uint_t) urealloc( (void*) r->esi, r->ecx );
			return;

		case SYSCALL_UFREE: // 37

			// free the memory
			ufree( (void*) r->esi );
			r->ebx = 0;
			return;
	}
}

/// System call int 0x80 stub
void SysCallInt128Stub( struct regs* r )
{
	// nothing more than calling the function
	SysCall( r );
}
